package jmu.ant.task.dependency ;


import java.io.BufferedReader ;
import java.io.BufferedWriter ;
import java.io.File ;
import java.io.FileNotFoundException ;
import java.io.FileReader ;
import java.io.FileWriter ;
import java.io.IOException ;
import java.util.LinkedList ;
import java.util.List ;

import org.apache.tools.ant.BuildException ;
import org.apache.tools.ant.Task ;

public class FindTask extends Task
{
    private static final String SRC = "src" ;

    // task attributes
    private String src = SRC ;
    private String dir ;
    private String file ;
    private String config ;

    // processing data
    private List < String > processed = new LinkedList < String > () ;
    private List < String > inprogress = new LinkedList < String > () ;

    public FindTask ()
    {
        setTaskName ( "dependencies" ) ;
    }

    @Override
    public void execute () throws BuildException
    {
        // validate parameters
        validate () ;

        // search for dependencies
        final File root = new File ( dir ) ;

        try
        {
            scan ( root ) ;
        }
        catch ( IOException e )
        {
            throw new BuildException ( "file system error", e ) ;
        }

        // store results
        store ( getFile (), processed ) ;

        // clean up results
        inprogress.clear () ;
        processed.clear () ;
    }

    public String getConfig ()
    {
        return config ;
    }

    public String getDir ()
    {
        return dir ;
    }

    public String getFile ()
    {
        return file ;
    }

    public String getSrc ()
    {
        return src ;
    }

    public void setConfig ( final String config )
    {
        this.config = config ;
    }

    public void setDir ( final String dir )
    {
        this.dir = dir ;
    }

    public void setFile ( String file )
    {
        this.file = file ;
    }

    public void setSrc ( final String src )
    {
        this.src = src ;
    }

    private List < File > findDependencies ( final File parent )
    {
        // store result
        List < File > list = new LinkedList < File > () ;

        // find dependencies
        final File[] files = parent.listFiles () ;
        if ( null != files )
        {
            for ( int i = 0 ; i < files.length ; i++ )
            {
                // ignore files
                if ( files[i].isFile () )
                {
                    continue ;
                }

                // ignore source folders
                if ( getSrc ().equalsIgnoreCase ( files[i].getName () ) )
                {
                    continue ;
                }

                list.add ( files[i] ) ;
            }
        }

        return list ;
    }

    private List < File > readDependencies ( File parent, File dependency )
    {
        List < File > list = new LinkedList < File > () ;
        try
        {
            final BufferedReader reader = new BufferedReader ( new FileReader ( dependency ) ) ;
            String l ;
            while ( null != (l = reader.readLine ()) )
            {
                File c = new File ( parent, l ) ;
                if ( !c.exists () )
                {
                    continue ;
                }

                list.add ( c ) ;
            }
            reader.close () ;
        }
        catch ( final FileNotFoundException e )
        {
            throw new BuildException ( "failed read dependency file", e ) ;
        }
        catch ( final IOException e )
        {
            throw new BuildException ( "failed read dependency file", e ) ;
        }

        return list ;
    }

    private void scan ( File parent ) throws IOException
    {
        // check if already processed
        final String absoluteFilePath = parent.getCanonicalPath () ;
        if ( processed.contains ( absoluteFilePath ) )
        {
            return ;
        }

        inprogress.add ( absoluteFilePath ) ;

        // get dependency file
        final File dependency = new File ( parent, getConfig () ) ;
        final List < File > children = dependency.exists () ? readDependencies ( parent, dependency )
                : findDependencies ( parent ) ;

        // recursively process children
        for ( File c : children )
        {
            // ignore cyclic dependencies
            if ( inprogress.contains ( c.getCanonicalPath () ) )
            {
                continue ;
            }

            // do scan
            scan ( c ) ;
        }

        // update current folder status
        inprogress.remove ( absoluteFilePath ) ;
        processed.add ( absoluteFilePath ) ;
    }

    private void store ( final String file, final List < String > dependencies )
    {
        try
        {
            BufferedWriter w = new BufferedWriter ( new FileWriter ( file ) ) ;
            for ( String d : dependencies )
            {
                w.write ( d.replace ( "\\", "/" ) ) ;
                w.newLine () ;
            }
            w.close () ;
        }
        catch ( IOException e )
        {
            throw new BuildException ( "failed store dependencies", e ) ;
        }
    }

    private void validate () throws BuildException
    {
        try
        {
            validateIsEmpty ( "src", getSrc () ) ;
            validateIsFile ( "config", getConfig () ) ;

            validateNull ( "dir", getDir () ) ;
            validateExist ( "dir", getDir () ) ;
            validateIsDir ( "dir", getDir () ) ;

            validateNull ( "file", getFile () ) ;
            validateIsFile ( "file", getFile () ) ;
        }
        catch ( final IllegalArgumentException e )
        {
            throw new BuildException ( "Failed falidate attribute values", e ) ;
        }
    }

    private void validateExist ( final String name, final File file )
    {
        if ( !file.exists () )
        {
            throw new IllegalArgumentException ( name + " doesn't exist" ) ;
        }
    }

    private void validateExist ( final String name, final String path )
    {
        validateExist ( name, new File ( path ) ) ;
    }

    private void validateIsDir ( final String name, final File file )
    {
        if ( file.exists () && !file.isDirectory () )
        {
            throw new IllegalArgumentException ( name + " is not a directory" ) ;
        }
    }

    private void validateIsDir ( final String name, final String dir )
    {
        validateIsDir ( name, new File ( dir ) ) ;
    }

    private void validateIsEmpty ( final String name, final String value )
    {
        if ( null != value && value.isEmpty () )
        {
            throw new IllegalArgumentException ( name + " can't be empty" ) ;
        }
    }

    private void validateIsFile ( final String name, final File file )
    {
        if ( file.exists () && !file.isFile () )
        {
            throw new IllegalArgumentException ( name + " is not a file" ) ;
        }
    }

    private void validateIsFile ( final String name, final String file )
    {
        validateIsFile ( name, new File ( file ) ) ;
    }

    private void validateNull ( final String name, final Object value )
    {
        if ( null == value )
        {
            throw new IllegalArgumentException ( name + " can't be null" ) ;
        }
    }
}
